"""
This file is part of GSBEdit. For copyright and licensing information, please
see gsbedit.py in the GSBEdit base directory.
"""


import wx
import string
import sys
import webbrowser

import app.module_config as mc
from infopanel import InfoPanel


ME_TEXT_NAME = 3001
ME_BTN_NEW_MODULE = 3002
ME_BTN_SAVE_MODULE = 3003
ME_BTN_DELETE_MODULE = 3004
ME_DPS_CHANGE = 3005
ME_BTN_ADD_MODULE = 3006
ME_BTN_OPEN_FILE = 3007



class AttributeGroup:
    def __init__(self):
        self.attributes = {}
        self.content = []
        self.bindings = []
        self.callbacks = {}
    
    def get_displayable_attributes(self, module_config):
        attr = []
        for item in self.content:
            if item['key'] in module_config['required'] or item['key'] in module_config['optional']:
                attr.append(item)
        return attr
    
    def should_display(self, module_config):
        return len(self.get_displayable_attributes(module_config)) > 0
    
    def get_subpanel_attr(self, module_config):
        items = self.get_displayable_attributes(module_config)
        return {'cols':1, 'items':[items]}
    
    def set_controls(self, ctrls):
        self.ctrls = ctrls
        
    
class Basic3Group(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        
        cost = {'type':'text', 'desc':'Cost', 'key':'cost', 'tip':'The cost to fit the module to a ship.'}
        weight = {'type':'text', 'desc':'Weight', 'key':'weight', 'tip':'The weight of the module.'}
        hitpoints = {'type':'text', 'desc':'Hitpoints', 'key':'hitpoints', 'tip':'The amount the damage the module can sustain before being destroyed.'}
        crew_required = {'type':'text', 'desc':'Crew requirement', 'key':'crew_required', 'tip':'The number of crew required to operate the module.'}
        powerconsumed = {'type':'text', 'desc':'Power consumption', 'key':'powerconsumed', 'tip':'The amount of power consumed by the module.'}
        
        self.content = [cost, weight, hitpoints, crew_required, powerconsumed]
        

class EngineGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        self.content = [{'type':'text', 'desc':'Thrust', 'key':'thrust', 'tip':'The thrust provided by the engine.'}]

        
class ArmourGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        self.content = [{'type':'text', 'desc':'Maximum damage absorption', 'key':'maxdamageabsorbable', 'tip':'The maximum amount of damage that the armour will absorb before being destroyed.'}]


class CrewGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        self.content = [{'type':'text', 'desc':'Crew supplied', 'key':'crewsupply', 'tip':'The number of crew members supplied by this module.'}]
        
        
class PowerGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        self.content = [{'type':'text', 'desc':'Power output', 'key':'powerproduced', 'tip':'The amount of power generated by this module.'}]


class MiscGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        self.content = [{'type':'text', 'desc':'Stack effectiveness', 'key':'stack_effectiveness', 'tip':'The performance multiplier when more than one module of this type is used. Should be less than 1.0 in most cases. Values above 1.0 are supported as of GSB v1.56.'}]
        



class Basic1Group(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        
        self.auto_internal = True
        
        ship_sizes = ['Cruiser', 'Frigate', 'Fighter']
        
        name     = {'type':'text',        'desc':'Name',             'key':'guiname',    'id':ME_TEXT_NAME, 'tip':'The name of the module as presented to the player.'}
        desc     = {'type':'big_text',    'desc':'Description',      'key':'description', 'tip':'The module description presented to the player. Usually includes brief background information.'}
        int_name = {'type':'text',        'desc':'Internal name',    'key':'name', 'tip':'The unique name of the module used internally by the game engine, not seen by the player.'}
        size     = {'type':'choice_drop', 'desc':'Target ship size', 'key':'size',       'valmod':string.capwords, 'options':ship_sizes, 'rvalmod':string.upper, 'tip':'The class of ship that the module will be usable for.'}
        self.content = [name, desc, int_name, size]
        self.bindings = [{'event':wx.EVT_TEXT, 'func':self.name_updated, 'id':ME_TEXT_NAME}]
        self.callbacks = {'module_change_pre':self.set_module_pre, 'module_change_post':self.set_module_post}
    
    
    def set_module_pre(self, module, module_config):
        self.auto_internal = False
    
    
    def set_module_post(self, module, module_config):
        self.auto_internal = True
        
    
    def name_updated(self, event):
        if self.auto_internal:
            name = self.ctrls['guiname'].GetValue()
            name = name.lower()
            name = name.replace(' ', '_')
            self.ctrls['name'].SetValue(name)


class Basic2Group(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        
        classes = ['SIM_ArmourModule', 'SIM_BeamWeaponModule', 'SIM_BulletWeaponModule', 'SIM_CamoflageShieldModule', 'SIM_CarrierModule', 'SIM_CrewModule', 'SIM_ECMBeamModule', 'SIM_ECMShieldModule', 'SIM_ECMShockModule', 'SIM_EngineModule', 'SIM_KineticWeapon', 'SIM_LightningGunModule', 'SIM_LimpetLauncher', 'SIM_MissileModule', 'SIM_PDScannerModule', 'SIM_PlasmaLauncherModule', 'SIM_PointDefenseModule', 'SIM_PowerModule', 'SIM_RepairModule', 'SIM_ShieldModule', 'SIM_SmartBombModule', 'SIM_TargetBoosterModule', 'SIM_TargetPainterModule', 'SIM_TractorModule']
        slottypes = ['Standard and turret', 'Turret only']
        categories = ['Weapons', 'Defenses', 'Engines', 'Other']
        
        note      = {'type':'caption',     'text':'Values on this page are automatically determined. This will usually provide the appropriate choices.', 'key':'note'}
        classname = {'type':'choice_drop', 'desc':'Class name', 'key':'classname', 'options':classes, 'tip':'The module type. The game engine will provide different behaviour for the module based on this.'}
        slottype  = {'type':'choice_drop', 'desc':'Slot type',  'key':'slot_type', 'valmod':self.convert_slot_type, 'options':slottypes, 'rvalmod':self.rconvert_slot_type, 'tip':'The type of slot that the module will fit into.'}
        category  = {'type':'choice_drop', 'desc':'Category', 'key':'category',  'valmod':string.capwords, 'options':categories, 'rvalmod':string.upper, 'tip':'The group that the module belongs in (used on the ship build screen).'}
        self.content = [note, classname, slottype, category]
    
    
    def convert_slot_type(self, slot_type):
        if slot_type == 'STANDARD':
            return 'Standard and turret'
        elif slot_type == 'TURRET':
            return 'Turret only'
        else:
            return 'Unknown slot type'
    
    
    def rconvert_slot_type(self, slot_type):
        if slot_type == 'Standard and turret':
            return 'STANDARD'
        elif slot_type == 'Turret only':
            return 'TURRET'
        else:
            return 'UNKNOWN'        
        

class CloakGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        
        power = {'type':'text', 'desc':'Max. cloak power', 'key':'max_cloak_power', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        recharge = {'type':'text', 'desc':'Recharge rate', 'key':'cloak_rechargerate', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        consumption = {'type':'text', 'desc':'Power consumption', 'key':'cloak_consumptionrate', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        self.content = [power, recharge, consumption]


class ECMGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        
        strength = {'type':'text', 'desc':'ECM strength', 'key':'ecm_strength', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        duration = {'type':'text', 'desc':'Shock duration', 'key':'shock_duration', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        self.content = [strength, duration]
        
        
class PDScannerGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        
        accuracy = {'type':'text', 'desc':'Scanner accuracy', 'key':'scanner_accuracy', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        self.content = [accuracy]


class RepairGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        
        repair_types = ['Armor']
        
        peace = {'type':'text', 'desc':'Peace interval', 'key':'peace_interval', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        repair_rate = {'type':'text', 'desc':'Repair rate', 'key':'repair_rate', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        supplies = {'type':'text', 'desc':'Supplies', 'key':'maxrepairsupplies', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        type  = {'type':'choice_drop', 'desc':'Repair type', 'key':'repair_type',  'valmod':string.capwords, 'options':repair_types, 'rvalmod':string.upper, 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        self.content = [peace, repair_rate, supplies, type]


class ShieldGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        
        points = {'type':'text', 'desc':'Shield points', 'key':'shieldpoints', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        resistance = {'type':'text', 'desc':'Resistance', 'key':'resistance', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        recharge_rate = {'type':'text', 'desc':'Recharge rate', 'key':'recharge_rate', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        self.content = [points, resistance, recharge_rate]


        
class TargetBoosterGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        
        boost = {'type':'text', 'desc':'Target boost', 'key':'targetboost', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        self.content = [boost]


class TractorBeamGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        
        weight = {'type':'text', 'desc':'Weight added', 'key':'beamweight', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        power = {'type':'text', 'desc':'Power capacity', 'key':'beampower', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        poweruse = {'type':'text', 'desc':'Power usage', 'key':'powerusage', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        recharge = {'type':'text', 'desc':'Recharge rate', 'key':'powerrecharge', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        self.content = [weight, power, poweruse, recharge]


class WeaponGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        
        fire_interval = {'type':'text', 'desc':'Firing interval', 'key':'fire_interval', 'tip':'The time between shots (in milliseconds).', 'id':ME_DPS_CHANGE}
        tracking_speed = {'type':'text', 'desc':'Tracking speed', 'key':'tracking_speed', 'tip':'The speed at which the turret can track targets.'}
        min_range = {'type':'text', 'desc':'Minimum range', 'key':'min_range', 'tip':'The minimum range of the weapon.'}
        opt_range = {'type':'text', 'desc':'Optimum range', 'key':'optimum_range', 'tip':'The optimum range of the weapon.'}
        max_range = {'type':'text', 'desc':'Maximum range', 'key':'max_range', 'tip':'The maximum range of the weapon.'}
        salvo_size = {'type':'text', 'desc':'Salvo size', 'key':'salvo_size', 'tip':'The number of shots that are fired in one salvo.'}
        salvo_interval = {'type':'text', 'desc':'Salvo interval', 'key':'salvo_interval', 'tip':'The time between salvos (in milliseconds).'}
        range = {'type':'text', 'desc':'Range', 'key':'range', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
                
        damage = {'type':'text', 'desc':'Damage', 'key':'damage', 'tip':'The damage caused to the target\'s hull if shields and armour are penetrated.', 'id':ME_DPS_CHANGE}
        armour_penetration = {'type':'text', 'desc':'Armour penetration', 'key':'armour_penetration', 'tip':'The maximum armour value that the weapon can penetrate.'}
        shield_penetration = {'type':'text', 'desc':'Shield penetration', 'key':'shield_penetration', 'tip':'The maximum shield resistance that the weapon can penetrate.'}
        
        spacer = {'type':'spacer', 'key':'spacer'}
        
        dps = {'type':'static_text', 'text':'Damage per second: -', 'key':'dps'}
        
        self.content = [fire_interval, damage, armour_penetration, shield_penetration, spacer, dps, tracking_speed, min_range, opt_range, max_range, range, salvo_size, salvo_interval]
        #self.bindings = [{'event':wx.EVT_TEXT, 'func':self.dps_updated, 'id':ME_DPS_CHANGE}]
        
        
    def get_subpanel_attr(self, module_config):
        items = self.get_displayable_attributes(module_config)
        
        col1_keys = ['fire_interval', 'damage', 'armour_penetration', 'shield_penetration', 'spacer', 'dps']
        col2_keys = ['tracking_speed', 'min_range', 'optimum_range', 'max_range', 'range', 'salvo_size', 'salvo_interval']
        
        col1 = []
        col2 = []
        for item in items:
            if item['key'] in col1_keys: col1.append(item)
            if item['key'] in col2_keys: col2.append(item)
        cols = [col1, col2]
        return {'cols':2, 'items':cols}


    def dps_updated(self, event):
        # We *should* have values for these, but maybe we don't
        try:
            fi = self.ctrls['fire_interval'].GetValue()
            dam = self.ctrls['damage'].GetValue()
            
            fi = float(fi)
            dam = float(dam)
            shots_per_second = 1. / (fi / 1000.)
            dps = shots_per_second * dam
            self.ctrls['dps'].SetLabel('Damage per second: %.2f' % dps)
        except:
            self.ctrls['dps'].SetLabel('Damage per second: -')
            
            
class RadiationInfo(AttributeGroup):
    def __init__(self, parent, window_parent, module_config):
        AttributeGroup.__init__(self, parent, window_parent, module_config)
        
        payload = {'type':'text', 'desc':'Payload type', 'key':'payload', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        radiation_damage = {'type':'text', 'desc':'Radiation damage', 'key':'radiation_damage', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        radiation_decay = {'type':'text', 'desc':'Radiation decay', 'key':'radiation_decay', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        total_damage = {'type':'text', 'desc':'Total damage', 'key':'total_damage', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        damage_duration = {'type':'text', 'desc':'Damage duration', 'key':'damage_duration', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        
        self.content_desc = [payload, radiation_damage, radiation_decay, total_damage, damage_duration]
        

class BeamGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        
        beamwidth = {'type':'text', 'desc':'Beam width', 'key':'beamwidth', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        beam_duration = {'type':'text', 'desc':'Beam duration', 'key':'beam_duration', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        firedelay = {'type':'text', 'desc':'Firing delay', 'key':'firedelay', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        beam_texture = {'type':'text', 'desc':'Beam texture', 'key':'beam_texture', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        has_grain = {'type':'checkbox', 'desc':'Has grain', 'key':'has_grain', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        grain = {'type':'text', 'desc':'Grain', 'key':'grain', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        convergence = {'type':'checkbox', 'desc':'Beams converge', 'key':'convergence', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        
        self.content = [beamwidth, beam_duration, firedelay, beam_texture, has_grain, grain, convergence]
                    

class WeaponUIGroup(AttributeGroup):
    def __init__(self):
        AttributeGroup.__init__(self)
        
        turret_sprite = {'type':'text', 'desc':'Turret sprite', 'key':'turret_sprite', 'tip':'The turret sprite to use in the game.'}
        turretsize = {'type':'text', 'desc':'Turret size', 'key':'turretsize', 'tip':'The size of the turret in the game.'}
        sound = {'type':'text', 'desc':'Sound', 'key':'sound', 'tip':'The sound to play when the weapon fires. Multiple randomised sounds are supported as of GSB v1.56.'}
        sound_volume = {'type':'text', 'desc':'Sound volume', 'key':'soundvolume', 'tip':'The volume at which to play the firing sound (0.0 to 1.0).'}
        color = {'type':'text', 'desc':'Colour', 'key':'color', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        blasttexture = {'type':'text', 'desc':'Blast texture', 'key':'blasttexture', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        texture = {'type':'text', 'desc':'Texture', 'key':'texture', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}
        
        self.content = [turret_sprite, turretsize, sound, sound_volume, color, blasttexture, texture]


class ModuleEditor(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        
        self.configs = mc.ModuleConfig('data/modules.cfg')
        
        self.parent = parent
        self.pages = []
        
        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
        
        header_sizer = wx.BoxSizer(wx.HORIZONTAL)
        font12bold = wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.FONTWEIGHT_BOLD)
        self.header = wx.StaticText(self, -1, 'No module selected')
        self.header.SetFont(font12bold)
        self.file_btn = wx.Button(self, ME_BTN_OPEN_FILE, 'Open module file')
        self.file_btn.Enable(False)
        header_sizer.Add(self.header)
        header_sizer.Add((0, 0), 1, wx.EXPAND)
        header_sizer.Add(self.file_btn)
        
        buttons = wx.BoxSizer(wx.HORIZONTAL)
        buttons.Add(wx.BitmapButton(self, ME_BTN_ADD_MODULE, wx.Bitmap('img/add.png')), 0, wx.EXPAND)
        buttons.Add((32, 1), 0)
        self.save_button = wx.BitmapButton(self, ME_BTN_SAVE_MODULE, wx.Bitmap('img/save.png'))
        self.save_button.Enable(False)
        buttons.Add(self.save_button, 0, wx.EXPAND)
        self.delete_button = wx.BitmapButton(self, ME_BTN_DELETE_MODULE, wx.Bitmap('img/delete.png'))
        self.delete_button.Enable(False)
        buttons.Add(self.delete_button, 0, wx.EXPAND)

        self.tabs = wx.Notebook(self)
        
        self.main_sizer.Add((1, 5), 0)
        self.main_sizer.Add(header_sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, border = 10)
        self.main_sizer.Add(buttons, 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border = 10)
        self.main_sizer.Add(self.tabs, 1, wx.EXPAND | wx.ALL, border = 10)

        self.SetSizer(self.main_sizer)
        
        self.Bind(wx.EVT_BUTTON, self.save_module_relay, id=ME_BTN_SAVE_MODULE)
        
        self.Bind(wx.EVT_BUTTON, self.add_module, id=ME_BTN_ADD_MODULE)
        self.Bind(wx.EVT_BUTTON, self.delete_module, id=ME_BTN_DELETE_MODULE)
        self.Bind(wx.EVT_BUTTON, self.open_file, id=ME_BTN_OPEN_FILE)
    
    
    def open_file(self, event):
        filename = self.module.get_filename()
        webbrowser.open(filename)
        
        
    def reverse_module_type_lookup(self, classname):
        module_type = self.configs.reverse_lookup(classname)
        if module_type == 'unknown':
            return 'custom'
        return module_type
    
    
    def set_module(self, module):
        self.save_prompt()
        
        if module.has_value('gsbedit', 'module_type'):
            module_type = module.get_value('gsbedit', 'module_type')
        else:
            module_type = module.get_value('config', 'classname')
            module_type = self.reverse_module_type_lookup(module_type)
        
        module_config = self.configs.get_config(module_type)
        
        self.Freeze()
        
        self.tabs.DeleteAllPages()
        self.pages = []
        
        self.header.SetLabel(module.get_name())
    
        groups = [('Basic 1', Basic1Group), ('Basic 2', Basic2Group), ('Basic 3', Basic3Group),
                  ('Engine', EngineGroup), ('Armour', ArmourGroup), ('Crew', CrewGroup), ('Power', PowerGroup),
                  ('Cloak', CloakGroup), ('ECM', ECMGroup), ('PD Scanner', PDScannerGroup), ('Repair', RepairGroup),
                  ('Shield', ShieldGroup), ('Target Booster', TargetBoosterGroup),
                  ('Weapon', WeaponGroup), ('Beam', BeamGroup), ('Tractor Beam', TractorBeamGroup), ('Weapon UI', WeaponUIGroup),
                  ('Misc', MiscGroup)]
        
        for name,group in groups:
            g = group()
            if g.should_display(module_config):
                panel = InfoPanel(self.tabs, g)
                panel.set_module(module, module_config)
                
                self.tabs.AddPage(panel, name)
                self.pages.append(panel)
        
        self.Thaw()
        
        self.module = module
        self.save_button.Enable()
        self.delete_button.Enable()
        self.file_btn.Enable()
        
    
    def add_module(self, event):
        self.parent.add_module()
    
    
    def delete_module(self, event):
        self.parent.delete_module()
    
    
    def has_edits(self):
        return True in [x.has_edits() for x in self.pages]
    
    
    def save_prompt(self):
        if self.has_edits():
            save_dlg = wx.MessageDialog(self, 'Do you wish to save your changes to this module?', 'Save changes?', 
    style=wx.YES_NO | wx.CENTRE | wx.ICON_QUESTION)
            result = save_dlg.ShowModal()
            if result == wx.ID_YES:
                self.save_module()
    
    
    def get_edits(self):
        edits = []
        map(lambda page: edits.extend(page.get_edits()), self.pages)
        return dict(edits)
    
    
    def save_module_relay(self, event):
        self.save_module()
    
    def save_module(self):
        edits = self.get_edits()
        self.module.update_values('config', edits)
        self.module.write_to_file()
        map(lambda page: page.update_orig_vals(), self.pages)
        self.parent.refresh_tree()


class ModuleEditorWindow(wx.Frame):
    def __init__(self, parent, module):
        wx.Frame.__init__(self, parent, -1, 'Module editor - %s' % module.get_name(), size=(600, 500))
        
        self.parent = parent        
        self.module = module
        
        self.Bind(wx.EVT_CLOSE, self.close)
        
        self.panel = ModuleEditor(self)
        self.panel.set_module(module)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.panel, 1, wx.EXPAND)

        self.SetSizer(sizer)
        
        self.Center()
        self.Show(True)
        self.Raise()
    
    
    def close(self, event):
        self.Destroy()
